/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 * */

// AmzServer - Tototoy's server model.
// Author: tonyjobmails@gmail.com (tonyhack).
//
// Define class TCPSocket.
//
// TCPSocket is a wrapper of socket and it only supports TCP connection.
//

#include <sys/types.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>

#include "tcp_socket.h"
#include "logging.h"

namespace amz {

TCPSocket::TCPSocket(int socket) : socket_(socket), link_available_(false) {
}

TCPSocket::~TCPSocket() {
  this->Close();
}

bool TCPSocket::CreateSocket() {
  this->socket_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if(this->socket_ == -1) {
    LOG(ERROR) << "Can't create a socket.";
    return false;
  }

  int opt = 0;
  if(setsockopt(this->socket_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)) != 0) {
    LOG(ERROR) << "Failed to setsockopt.";
    return false;
  }
  return true;
}

bool TCPSocket::Connect(const char *ip, uint16 port) {
  struct sockaddr_in server_addr;
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(port);

  if(inet_pton(AF_INET, ip, &server_addr.sin_addr) > 0 &&
     connect(socket_, (sockaddr*)&server_addr, sizeof(server_addr)) >= 0) {
    this->SetAvailable();
    return true;
  }

  // Failed, close socket
  this->Close();
  LOG(ERROR) << "Connect failed to " << ip << ":" << port;

  return false;
}

bool TCPSocket::Bind(const char *ip, uint16 port) {
  sockaddr_in server_addr;
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  server_addr.sin_port = htons(80);

  if(inet_pton(AF_INET, ip, &server_addr.sin_addr) > 0 &&
     bind(this->socket_, (sockaddr *)&server_addr, sizeof(server_addr)) >= 0) {
    this->SetAvailable();
    return true;
  }

  // Failed, close socket
  this->Close();
  LOG(ERROR) << "Bind failed to " << ip << ":" << port;

  return false;
}

bool TCPSocket::Listen(int max_connection) {
  if(listen(this->socket_, max_connection) > 0) {
    return true;
  }

  // Failed, close socket
  this->Close();
  LOG(ERROR) << "Listen failed";

  return false;
}

bool TCPSocket::Accept(TCPSocket *tcp_socket, std::string *ip, uint16 *port) {
  sockaddr_in client_addr;
  socklen_t len = sizeof(client_addr);

  int sock_client = accept(this->socket_, (sockaddr *)&client_addr, &len);
  if(sock_client < 0) {
    LOG(ERROR) << "Accept failed.";
    return false;
  }

  // Convert to a string ip.
  char temp_ip[INET_ADDRSTRLEN];
  const char *ip_client = inet_ntop(AF_INET, &client_addr.sin_addr, temp_ip, sizeof(temp_ip));
  if(ip_client == NULL) {
    LOG(ERROR) << "Accept failed on inet_ntop.";
    return false;
  }

  *ip = ip_client;
  // Convert to localhost byte order.
  *port = ntohs(client_addr.sin_port);
  tcp_socket->socket_ = sock_client;
  tcp_socket->SetAvailable();

  return true;
}

bool TCPSocket::Shutdown(int shut_flag) {
  return shutdown(this->socket_, shut_flag) == 0;
}

bool TCPSocket::Close() {
  if(this->socket_ >= 0)
    if(close(this->socket_) != 0) {
      LOG(ERROR) << "Close error.";
      return false;
    }

  return true;
}

int TCPSocket::Send(const char *message, int size) {
  return send(this->socket_, message, size, MSG_NOSIGNAL);
}

int TCPSocket::Receive(char *message, int size) {
  size = recv(this->socket_, message, size, MSG_NOSIGNAL);
  if(size == 0) this->SetUnavailable();
  return size;
}

bool TCPSocket::SetNonblock() {
  int flags = 0;
  flags = fcntl(this->socket_, F_GETFL, 0);
  if(fcntl(this->socket_, F_SETFL, flags | O_NONBLOCK) < 0) {
    LOG(ERROR) << "SetNonblock error on fcntl.";
    return false;
  }

  return true;
}

}  // namespace amz

